home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2017 October / PCgo 10-2017 CD-ROM Germany.iso / nw.pak / Unnamed File 005246.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  13.0 KB  |  326 lines

  1. "use strict";
  2. /*
  3.  * Copyright (C) 2012 Google Inc. All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice, this list of conditions and the following disclaimer in the
  12.  *    documentation and/or other materials provided with the distribution.
  13.  *
  14.  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
  15.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17.  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  18.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  21.  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  23.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24.  */
  25.  
  26. /**
  27.  * @constructor
  28.  * @param {!Element} element
  29.  * @param {!Object} config
  30.  */
  31. function SuggestionPicker(element, config) {
  32.     Picker.call(this, element, config);
  33.     this._isFocusByMouse = false;
  34.     this._containerElement = null;
  35.     this._setColors();
  36.     this._layout();
  37.     this._fixWindowSize();
  38.     this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this);
  39.     document.body.addEventListener("keydown", this._handleBodyKeyDownBound);
  40.     this._element.addEventListener("mouseout", this._handleMouseOut.bind(this), false);
  41. }
  42. SuggestionPicker.prototype = Object.create(Picker.prototype);
  43.  
  44. SuggestionPicker.NumberOfVisibleEntries = 20;
  45.  
  46. // An entry needs to be at least this many pixels visible for it to be a visible entry.
  47. SuggestionPicker.VisibleEntryThresholdHeight = 4;
  48.  
  49. SuggestionPicker.ActionNames = {
  50.     OpenCalendarPicker: "openCalendarPicker"
  51. };
  52.  
  53. SuggestionPicker.ListEntryClass = "suggestion-list-entry";
  54.  
  55. SuggestionPicker.validateConfig = function(config) {
  56.     if (config.showOtherDateEntry && !config.otherDateLabel)
  57.         return "No otherDateLabel.";
  58.     if (config.suggestionHighlightColor && !config.suggestionHighlightColor)
  59.         return "No suggestionHighlightColor.";
  60.     if (config.suggestionHighlightTextColor && !config.suggestionHighlightTextColor)
  61.         return "No suggestionHighlightTextColor.";
  62.     if (config.suggestionValues.length !== config.localizedSuggestionValues.length)
  63.         return "localizedSuggestionValues.length must equal suggestionValues.length.";
  64.     if (config.suggestionValues.length !== config.suggestionLabels.length)
  65.         return "suggestionLabels.length must equal suggestionValues.length.";
  66.     if (typeof config.inputWidth === "undefined")
  67.         return "No inputWidth.";
  68.     return null;
  69. };
  70.  
  71. SuggestionPicker.prototype._setColors = function() {
  72.     var text = "." + SuggestionPicker.ListEntryClass + ":focus {\
  73.         background-color: " + this._config.suggestionHighlightColor + ";\
  74.         color: " + this._config.suggestionHighlightTextColor + "; }";
  75.     text += "." + SuggestionPicker.ListEntryClass + ":focus .label { color: " + this._config.suggestionHighlightTextColor + "; }";
  76.     document.head.appendChild(createElement("style", null, text));
  77. };
  78.  
  79. SuggestionPicker.prototype.cleanup = function() {
  80.     document.body.removeEventListener("keydown", this._handleBodyKeyDownBound, false);
  81. };
  82.  
  83. /**
  84.  * @param {!string} title
  85.  * @param {!string} label
  86.  * @param {!string} value
  87.  * @return {!Element}
  88.  */
  89. SuggestionPicker.prototype._createSuggestionEntryElement = function(title, label, value) {
  90.     var entryElement = createElement("li", SuggestionPicker.ListEntryClass);
  91.     entryElement.tabIndex = 0;
  92.     entryElement.dataset.value = value;
  93.     var content = createElement("span", "content");
  94.     entryElement.appendChild(content);
  95.     var titleElement = createElement("span", "title", title);
  96.     content.appendChild(titleElement);
  97.     if (label) {
  98.         var labelElement = createElement("span", "label", label);
  99.         content.appendChild(labelElement);
  100.     }
  101.     entryElement.addEventListener("mouseover", this._handleEntryMouseOver.bind(this), false);
  102.     return entryElement;
  103. };
  104.  
  105. /**
  106.  * @param {!string} title
  107.  * @param {!string} actionName
  108.  * @return {!Element}
  109.  */
  110. SuggestionPicker.prototype._createActionEntryElement = function(title, actionName) {
  111.     var entryElement = createElement("li", SuggestionPicker.ListEntryClass);
  112.     entryElement.tabIndex = 0;
  113.     entryElement.dataset.action = actionName;
  114.     var content = createElement("span", "content");
  115.     entryElement.appendChild(content);
  116.     var titleElement = createElement("span", "title", title);
  117.     content.appendChild(titleElement);
  118.     entryElement.addEventListener("mouseover", this._handleEntryMouseOver.bind(this), false);
  119.     return entryElement;
  120. };
  121.  
  122. /**
  123. * @return {!number}
  124. */
  125. SuggestionPicker.prototype._measureMaxContentWidth = function() {
  126.     // To measure the required width, we first set the class to "measuring-width" which
  127.     // left aligns all the content including label.
  128.     this._containerElement.classList.add("measuring-width");
  129.     var maxContentWidth = 0;
  130.     var contentElements = this._containerElement.getElementsByClassName("content");
  131.     for (var i=0; i < contentElements.length; ++i) {
  132.         maxContentWidth = Math.max(maxContentWidth, contentElements[i].offsetWidth);
  133.     }
  134.     this._containerElement.classList.remove("measuring-width");
  135.     return maxContentWidth;
  136. };
  137.  
  138. SuggestionPicker.prototype._fixWindowSize = function() {
  139.     var ListBorder = 2;
  140.     var desiredWindowWidth = this._measureMaxContentWidth() + ListBorder;
  141.     if (typeof this._config.inputWidth === "number")
  142.         desiredWindowWidth = Math.max(this._config.inputWidth, desiredWindowWidth);
  143.     var totalHeight = ListBorder;
  144.     var maxHeight = 0;
  145.     var entryCount = 0;
  146.     for (var i = 0; i < this._containerElement.childNodes.length; ++i) {
  147.         var node = this._containerElement.childNodes[i];
  148.         if (node.classList.contains(SuggestionPicker.ListEntryClass))
  149.             entryCount++;
  150.         totalHeight += node.offsetHeight;
  151.         if (maxHeight === 0 && entryCount == SuggestionPicker.NumberOfVisibleEntries)
  152.             maxHeight = totalHeight;
  153.     }
  154.     var desiredWindowHeight = totalHeight;
  155.     if (maxHeight !== 0 && totalHeight > maxHeight) {
  156.         this._containerElement.style.maxHeight = (maxHeight - ListBorder) + "px";
  157.         desiredWindowWidth += getScrollbarWidth();
  158.         desiredWindowHeight = maxHeight;
  159.         this._containerElement.style.overflowY = "scroll";
  160.     }
  161.  
  162.     var windowRect = adjustWindowRect(desiredWindowWidth, desiredWindowHeight, desiredWindowWidth, 0);
  163.     this._containerElement.style.height = (windowRect.height - ListBorder) + "px";
  164.     setWindowRect(windowRect);
  165. };
  166.  
  167. SuggestionPicker.prototype._layout = function() {
  168.     if (this._config.isRTL)
  169.         this._element.classList.add("rtl");
  170.     if (this._config.isLocaleRTL)
  171.         this._element.classList.add("locale-rtl");
  172.     this._containerElement = createElement("ul", "suggestion-list");
  173.     this._containerElement.addEventListener("click", this._handleEntryClick.bind(this), false);
  174.     for (var i = 0; i < this._config.suggestionValues.length; ++i) {
  175.         this._containerElement.appendChild(this._createSuggestionEntryElement(this._config.localizedSuggestionValues[i], this._config.suggestionLabels[i], this._config.suggestionValues[i]));
  176.     }
  177.     if (this._config.showOtherDateEntry) {
  178.         // Add separator
  179.         var separator = createElement("div", "separator");
  180.         this._containerElement.appendChild(separator);
  181.  
  182.         // Add "Other..." entry
  183.         var otherEntry = this._createActionEntryElement(this._config.otherDateLabel, SuggestionPicker.ActionNames.OpenCalendarPicker);
  184.         this._containerElement.appendChild(otherEntry);
  185.     }
  186.     this._element.appendChild(this._containerElement);
  187. };
  188.  
  189. /**
  190.  * @param {!Element} entry
  191.  */
  192. SuggestionPicker.prototype.selectEntry = function(entry) {
  193.     if (typeof entry.dataset.value !== "undefined") {
  194.         this.submitValue(entry.dataset.value);
  195.     } else if (entry.dataset.action === SuggestionPicker.ActionNames.OpenCalendarPicker) {
  196.         window.addEventListener("didHide", SuggestionPicker._handleWindowDidHide, false);
  197.         hideWindow();
  198.     }
  199. };
  200.  
  201. SuggestionPicker._handleWindowDidHide = function() {
  202.     openCalendarPicker();
  203.     window.removeEventListener("didHide", SuggestionPicker._handleWindowDidHide);
  204. };
  205.  
  206. /**
  207.  * @param {!Event} event
  208.  */
  209. SuggestionPicker.prototype._handleEntryClick = function(event) {
  210.     var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
  211.     if (!entry)
  212.         return;
  213.     this.selectEntry(entry);
  214.     event.preventDefault();
  215. };
  216.  
  217. /**
  218.  * @return {?Element}
  219.  */
  220. SuggestionPicker.prototype._findFirstVisibleEntry = function() {
  221.     var scrollTop = this._containerElement.scrollTop;
  222.     var childNodes = this._containerElement.childNodes;
  223.     for (var i = 0; i < childNodes.length; ++i) {
  224.         var node = childNodes[i];
  225.         if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
  226.             continue;
  227.         if (node.offsetTop + node.offsetHeight - scrollTop > SuggestionPicker.VisibleEntryThresholdHeight)
  228.             return node;
  229.     }
  230.     return null;
  231. };
  232.  
  233. /**
  234.  * @return {?Element}
  235.  */
  236. SuggestionPicker.prototype._findLastVisibleEntry = function() {
  237.     var scrollBottom = this._containerElement.scrollTop + this._containerElement.offsetHeight;
  238.     var childNodes = this._containerElement.childNodes;
  239.     for (var i = childNodes.length - 1; i >= 0; --i){
  240.         var node = childNodes[i];
  241.         if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
  242.             continue;
  243.         if (scrollBottom - node.offsetTop > SuggestionPicker.VisibleEntryThresholdHeight)
  244.             return node;
  245.     }
  246.     return null;
  247. };
  248.  
  249. /**
  250.  * @param {!Event} event
  251.  */
  252. SuggestionPicker.prototype._handleBodyKeyDown = function(event) {
  253.     var eventHandled = false;
  254.     var key = event.keyIdentifier;
  255.     if (key === "U+001B") { // ESC
  256.         this.handleCancel();
  257.         eventHandled = true;
  258.     } else if (key == "Up") {
  259.         if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
  260.             for (var node = document.activeElement.previousElementSibling; node; node = node.previousElementSibling) {
  261.                 if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
  262.                     this._isFocusByMouse = false;
  263.                     node.focus();
  264.                     break;
  265.                 }
  266.             }
  267.         } else {
  268.             this._element.querySelector("." + SuggestionPicker.ListEntryClass + ":last-child").focus();
  269.         }
  270.         eventHandled = true;
  271.     } else if (key == "Down") {
  272.         if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
  273.             for (var node = document.activeElement.nextElementSibling; node; node = node.nextElementSibling) {
  274.                 if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
  275.                     this._isFocusByMouse = false;
  276.                     node.focus();
  277.                     break;
  278.                 }
  279.             }
  280.         } else {
  281.             this._element.querySelector("." + SuggestionPicker.ListEntryClass + ":first-child").focus();
  282.         }
  283.         eventHandled = true;
  284.     } else if (key === "Enter") {
  285.         this.selectEntry(document.activeElement);
  286.         eventHandled = true;
  287.     } else if (key === "PageUp") {
  288.         this._containerElement.scrollTop -= this._containerElement.clientHeight;
  289.         // Scrolling causes mouseover event to be called and that tries to move the focus too.
  290.         // To prevent flickering we won't focus if the current focus was caused by the mouse.
  291.         if (!this._isFocusByMouse)
  292.             this._findFirstVisibleEntry().focus();
  293.         eventHandled = true;
  294.     } else if (key === "PageDown") {
  295.         this._containerElement.scrollTop += this._containerElement.clientHeight;
  296.         if (!this._isFocusByMouse)
  297.             this._findLastVisibleEntry().focus();
  298.         eventHandled = true;
  299.     }
  300.     if (eventHandled)
  301.         event.preventDefault();
  302. };
  303.  
  304. /**
  305.  * @param {!Event} event
  306.  */
  307. SuggestionPicker.prototype._handleEntryMouseOver = function(event) {
  308.     var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
  309.     if (!entry)
  310.         return;
  311.     this._isFocusByMouse = true;
  312.     entry.focus();
  313.     event.preventDefault();
  314. };
  315.  
  316. /**
  317.  * @param {!Event} event
  318.  */
  319. SuggestionPicker.prototype._handleMouseOut = function(event) {
  320.     if (!document.activeElement.classList.contains(SuggestionPicker.ListEntryClass))
  321.         return;
  322.     this._isFocusByMouse = false;
  323.     document.activeElement.blur();
  324.     event.preventDefault();
  325. };
  326.